package com.example.lua_redis_demo;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class LuaService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 生成id
     * @return
     */
    public String genId(){
        return execLua4String("genId.lua", Collections.singletonList("fkfjskjfs"));
    }

    /**
     * ip限流
     * @return
     */
    public Long rateLimit(){
        return Long.parseLong(execLua4String("ratelimiting.lua",Collections.singletonList("ip:192.168.11.204"),"10","3"));
    }

    public String putTestObejct() {
        Map<String,String> map2 = new HashMap<>();
        map2.put("key1","value1");
        map2.put("key2","value2");
        map2.put("key3","value3");
        map2.put("key4","value4");
        DemoDto demoDto = new DemoDto()
                .setId(14354534L)
                .setName("demoDTO")
                .setNums(5)
                .setIsValid(true)
                .setLsits(Arrays.asList("hello", "world"))
                .setMaps(map2)
                .setBxx(new Bxx()
                        .setId(5830232L).setIsValid(false).setName("dakhda").setNums(99).setLsits(Arrays.asList("hello", "world"))
                );
        redisTemplate.opsForValue().set("testObject",demoDto);
        return "success";
    }

    public DemoDto getTestObject() {
        return execLua4Object("testObject.lua",Collections.singletonList("testObject"),DemoDto.class);
    }

    /**
     * setIfAbsent：如果不存在，则设置。
     * setIfPresent：如果存在，则设置。
     * 加锁时 key 同，value 不同。
     * 释放锁时，根据value判断，是不是我的锁，不能释放别人的锁。
     * 及时释放锁，而不是利用自动超时。
     * 锁超时时间一定要结合业务情况权衡，过长，过短都不行。
     * 程序异常之处，要捕获，并释放锁。如果需要回滚的，主动做回滚、补偿。保证整体的健壮性，一致性。
     * @return
     */
    public Boolean lock(String key,String name){
        return stringRedisTemplate.opsForValue().setIfAbsent(key,name);
    }
    public Boolean lock(String key, String name,  long timeout, TimeUnit unit){
        long convertTimeOut = unit.convert(timeout, TimeUnit.SECONDS);
        String result = execLua4String("setnx.lua",Collections.singletonList(key),name,String.valueOf(convertTimeOut));
        return "1".equals(result);
    }
    /**
     * redis 如果要释放分布式锁，要么等过期时间被动释放，要么del key主动
     * 主动释放时可能存在问题
     * 线程1 拿到锁  超时了，被动释放了，线程1在finally里需要执行del。
     * 但是del之前线程2已经获得了锁，所以线程1 del了线程2的锁。失去了分布式锁的作用
     * 所以del时，我们需要判断锁是不是还在我身上。
     * 通过设置value为线程name 每次去判断 value == myThreadName 从而做判断，如果放在java中判断，失去了原子性。
     * lua脚本执行更高效，可靠
     * @param key
     * @param threadName
     * @return
     */
    public Boolean releaseLock(String key,String threadName) {
        String testReleaseLock = execLua4String("releaselock.lua", Collections.singletonList(key), threadName);
        return "1".equals(testReleaseLock);
    }

    /**
     * Redis分布式锁不靠谱的情况
     * 上面我们说的是redis，是单点的情况。如果是在redis sentinel集群中情况就有所不同了。
     * 关于redis sentinel 集群可以看这里。在redis sentinel集群中，我们具有多台redis，
     * 他们之间有着主从的关系，例如一主二从。我们的set命令对应的数据写到主库，然后同步到从库。
     * 当我们申请一个锁的时候，对应就是一条命令 setnx mykey myvalue ，在redis sentinel集群中，
     * 这条命令先是落到了主库。假设这时主库down了，而这条数据还没来得及同步到从库，sentinel将从库中的一台选举为主库了。
     * 这时，我们的新主库中并没有mykey这条数据，若此时另外一个client执行 setnx mykey hisvalue , 也会成功，即也能得到锁。
     * 这就意味着，此时有两个client获得了锁。这不是我们希望看到的，虽然这个情况发生的记录很小，
     * 只会在主从failover的时候才会发生，大多数情况下、大多数系统都可以容忍，但是不是所有的系统都能容忍这种瑕疵。
     *
     * redlock
     * 为了解决故障转移情况下的缺陷，Antirez 发明了 Redlock 算法，使用redlock算法，需要多个redis实例，
     * 加锁的时候，它会想多半节点发送 setex mykey myvalue 命令，只要过半节点成功了，那么就算加锁成功了。
     * 释放锁的时候需要想所有节点发送del命令。这是一种基于【大多数都同意】的一种机制。感兴趣的可以查询相关资料。
     * 在实际工作中使用的时候，我们可以选择已有的开源实现，python有redlock-py，java 中有Redisson redlock。
     *
     * redlock确实解决了上面所说的“不靠谱的情况”。但是，它解决问题的同时，也带来了代价。
     * 你需要多个redis实例，你需要引入新的库 代码也得调整，性能上也会有损。所以，果然是不存在“完美的解决方案”，
     * 我们更需要的是能够根据实际的情况和条件把问题解决了就好。
     */

    private String execLua4String(String fileName, List<String>keys, String ... values){
        DefaultRedisScript<String> redisScript =new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("lua/"+fileName));
        redisScript.setResultType(String.class);
        return stringRedisTemplate.execute(redisScript,keys,values);
    }

    private <T> T execLua4Object(String fileName, List<String>keys,Class<T> resultType, String ... values){
        DefaultRedisScript<T> redisScript =new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("lua/"+fileName));
        redisScript.setResultType(resultType);
        return redisTemplate.execute(redisScript,keys,values);
    }

    public static void main(String[] args) throws JsonProcessingException {
        /**
         * 貌似ImmutableMap 无法反序列化
         */
//        ImmutableMap<String, String> map = ImmutableMap.<String, String>of("key1", "value1", "key2", "value2", "key3", "value3");
//        GenericJackson2JsonRedisSerializer redisSerializer = new GenericJackson2JsonRedisSerializer("type");
////        byte[] bytes = redisSerializer.serialize(map);
////
////        ImmutableMap deserialize = redisSerializer.deserialize(bytes, ImmutableMap.class);
////        System.out.println(deserialize);
////
////        Jackson2JsonRedisSerializer<String> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(String.class);
////
////        ObjectMapper objectMapper = new ObjectMapper();
////        String s = objectMapper.writeValueAsString(map);
////
////        System.out.println(s);
////        final JavaType type = objectMapper.getTypeFactory().constructType(new TypeReference<ImmutableMap<String,String>>() { });
////        ImmutableMap<String,String> immutableMap = objectMapper.readValue(s, type);
////        System.out.println(immutableMap);
//        String s = JSONUtil.toJsonStr(map);
//        System.out.println(s);
//        ImmutableMap immutableMap = JSONUtil.toBean(s, ImmutableMap.class);
//        System.out.println(immutableMap);

    }

    //  scan 0  match  key*  count 20
    //SCAN cursor [MATCH pattern] [COUNT count] The default COUNT value is 10.
    public List<Object> mget(String name1, String name2, String name3) {
        List<String> strings = stringRedisTemplate.opsForValue().multiGet(Arrays.asList(name1, name2, name3));
        return execLua4Object("testMget.lua", Arrays.asList(name1, name2, name3), List.class);
    }
}
